package owg.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;


public class ClassFinder
	{
	/**List of all known class names*/
	private static String[] classNames;
	/**
	 * This method will search the source ("src") folder for java files, if it exists. 
	 * It will assume that they are class files and write them to a file "res/classes.txt".
	 * If there is no source folder, it will read the previously generated classes.txt file, looking for class names.
	 * @return A list of all class names, including package name.
	 */
	private static String[] scanSource(File generatedFilesFolder)
		{
		generatedFilesFolder.mkdirs();
		
		File sourceFolder = new File("src");
		File sourceFile = new File(generatedFilesFolder, "classes.txt");
		if(sourceFolder.isDirectory())
			{
			System.out.print("Searching "+sourceFolder+" for java class files... ");
			PrintWriter pw = null;
			try
				{
				pw = new PrintWriter(sourceFile);
				pw.println("Class name list. Do not touch.");
				ArrayList<String> fileList = new ArrayList<String>();
				searchSourceDir(sourceFolder, fileList, "");
				String[] result = new String[fileList.size()];
				fileList.toArray(result);
				pw.println(result.length);
				for(int i = 0; i<result.length; i++)
					{
					pw.println(result[i]);
					}
				System.out.println("Success: Found "+result.length+" class names: "+Arrays.toString(result));
				return result;
				}
			catch (IOException e)
				{
				throw new RuntimeException(e);
				}
			finally
				{
				try{pw.close();}catch(Exception woof){}
				}
			}
		else
			{
			System.out.print("Reading class names from "+sourceFile+"... ");
			if(!sourceFile.exists())
				throw new RuntimeException("Error: The game cannot find res/classes.txt and there is no source folder!");
			BufferedReader br = null;
			int numClasses = -1;
			String[] result;
			try
				{
				br = new BufferedReader(new FileReader(sourceFile));
				br.readLine();
				numClasses = Integer.parseInt(br.readLine());
				result = new String[numClasses];
				for(int i = 0; i<numClasses; i++)
					{
					result[i] = br.readLine();
					}
				System.out.println("Success: Found "+result.length+" class names: "+Arrays.toString(result));
				return result;
				}
			catch (IOException e)
				{
				if(numClasses == -1)
					throw new RuntimeException("Error: Could not read "+sourceFile+"!", e);
				else
					throw new RuntimeException("Error: Could not read "+sourceFile+"! Expected "+numClasses+" class names.", e);
				}
			catch (NumberFormatException e)
				{
				throw new RuntimeException("Error: Expected integer at line 2 of "+sourceFile+"!", e);
				}
			finally
				{
				try {br.close();} catch (Exception e){}
				}
			}
		}
	/**
	 * Recursively search the source folder for java class files. Class names with package prefixes will be added to the list.
	 * @param sourceFolder The current folder to search. The search will be recursive.
	 * @param classNameList The list of class names. File names with package prefixes will be added to this list.
	 * @param prefix The package prefix(for instance: "java.lang."). Should be the empty string if searching the root of the source directory.
	 */
	private static void searchSourceDir(File sourceFolder, ArrayList<String> classNameList, String prefix)
		{
		File[] files = sourceFolder.listFiles();
		for(File f: files)
			{
			if(f.isDirectory())
				{
				searchSourceDir(f, classNameList, prefix+f.getName()+".");
				}
			else if(f.getName().endsWith(".java") && !(f.getName().equals("Dummy.java")))//Dummy should never be instantiated by the user
				{
				String s = f.getName();
				classNameList.add(prefix+s.substring(0, s.length()-5));//Length of ".java" is 5.
				}
			}
		}
	/**
	 * This method will locate all subclasses of the indicated parent class in the specified package. Subpackages will be traversed if recursive is true.
	 * @param packageFolderName The full name of the package, with dots(no dot at end).
	 * @param recursive Whether to do a recursive search in packages or just use the top level of the indicated package
	 * @param parentClass The object representing the class for which to find subclasses.
	 * @param includeAbstract Whether to include abstract classes and interfaces
	 * @return A list of objects representing all subclasses(and possibly the parentclass).
	 */
	public static <T> ArrayList<Class<? extends T>> getSubclassesIn(File generatedFilesFolder, String packageName, boolean recursive, boolean includeAbstract, Class<T> parentClass)
		{
		if(classNames == null)
			classNames = scanSource(generatedFilesFolder);
		
		int packageDepth = Calc.countOccurrences(packageName, '.')+1;
		
		ArrayList<Class<? extends T>> list = new ArrayList<Class<? extends T>>();	
		for(int i=0; i<classNames.length; i++)
			{
			String str = classNames[i];
			if(str.startsWith(packageName) && (recursive || Calc.countOccurrences(str, '.') == packageDepth))
				{
				try
					{
					Class<?> c = Class.forName(str);
					if (parentClass.isAssignableFrom(c) && (includeAbstract || (!Modifier.isAbstract(c.getModifiers()) && !Modifier.isInterface(c.getModifiers()))))
						list.add(c.asSubclass(parentClass));
					} 
				catch (ClassNotFoundException e)
					{
					throw new RuntimeException("Faulty class name: "+str, e);
					}
				}
			}
		return list;
		}
	}
